// ==UserScript==
// @name         LLM Web2API - ChatGPT
// @namespace    http://tampermonkey.net/
// @version      2025-01-15
// @description  将ChatGPT Web端转换为API接口，绕过CSP限制
// @author       清风
// @match        https://chatgpt.com/*
// @match        https://chat.openai.com/*
// @run-at       document-start
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// ==/UserScript==
(function () {
    'use strict';

    // 服务器域名配置
    // 从url获取server_url
    let SERVER_URL = '';
    const server_url = new URLSearchParams(window.location.search).get('server_url');
    if (server_url) {
        SERVER_URL = server_url;
    }else {
        SERVER_URL = 'http://127.0.0.1:8123';
    }

    // 获取当前的url中获取参数role
    const urlParams = new URLSearchParams(window.location.search);
    const role = urlParams.get('role');
    if (role == "master") {
        // master 负责去api获取要干的任务ID列表
        // 开启新页面（worker），并把任务ID列表传给worker

        // resp: {
        // "after_time": 1000, // 下次获取任务列表的时间戳
        // "list": ["x12", "ysf2", "x13"] // 任务ID列表
        // }

        // 根据返回的after_time，设置一个定时器，定时器到时后，再次获取任务列表

        // 从服务器获取任务列表
        GM_xmlhttpRequest({
            method: 'GET',
            url: `${SERVER_URL}/questions`,
            headers: {
                'Content-Type': 'application/json'
            },
            onload: function(response) {
                try {
                    const resp = JSON.parse(response.responseText);
                    console.log('获取任务列表成功:', resp);
                    
                    // 遍历任务列表,为每个任务开启worker页面
                    resp.forEach(taskId => {
                        // 构建worker页面URL,带上role和taskId参数
                        const workerUrl = `${window.location.origin}?role=worker&taskId=${taskId}&server_url=${SERVER_URL}`;
                        // 打开新窗口
                        window.open(workerUrl, '_blank');
                    });

                    // 根据返回的after_time设置定时器
                    setTimeout(() => {
                        // 定时器到时后再次获取任务列表
                        window.location.reload();
                    }, 10000); // 计算剩余时间
                } catch (error) {
                    console.error('解析任务列表失败:', error);
                }
            },
            onerror: function(error) {
                console.error('获取任务列表失败:', error);
            }
        });
        return;
    }

    if (role != "worker") {
        console.error('role is not set');
        return;
    }
    // worker根据任务ID去服务器获取任务详情
    // 根据任务详情，调用askAndGetReply函数，获取回答
    // 将回答投送到API端
    // 更新任务状态为已完成
  
    async function waitForSelector(selector, { timeoutMs = 30000, root = document } = {}) {
      const start = performance.now();
      let el = root.querySelector(selector);
      if (el) return el;
      return new Promise((resolve, reject) => {
        const obs = new MutationObserver(() => {
          el = root.querySelector(selector);
          if (el) { obs.disconnect(); resolve(el); }
        });
        obs.observe(root, { childList: true, subtree: true });
        const timer = setInterval(() => {
          if (performance.now() - start > timeoutMs) {
            clearInterval(timer); obs.disconnect();
            reject(new Error('waitForSelector timeout: ' + selector));
          }
        }, 250);
      });
    }
  
    function getChatRoot() {
      return document.querySelector('[data-testid="conversation-turns"]')
          || document.querySelector('[role="feed"]')
          || document.querySelector('main')
          || document.body;
    }
  
    function hasStopButton() {
      const buttons = Array.from(document.querySelectorAll('button'));
      const byText = buttons.some(b => /停止|Stop/i.test(b.textContent || ''));
      const byTestId = buttons.some(b => /stop/i.test(b.getAttribute('data-testid') || b.getAttribute('testid') || ''));
      return byText || byTestId;
    }
  
    function waitForReplyComplete({ quietMs = 1500, timeoutMs = 600000, container = getChatRoot() } = {}) {
      return new Promise((resolve, reject) => {
        let quietTimer = null;
        const startTime = Date.now();
        
        // 添加进度提示
        const progressInterval = setInterval(() => {
          const elapsed = Math.floor((Date.now() - startTime) / 1000);
          const remaining = Math.floor((timeoutMs - (Date.now() - startTime)) / 1000);
          console.log(`AI回复处理中... 已用时: ${elapsed}s, 剩余时间: ${remaining}s`);
        }, 30000); // 每30秒显示一次进度
        
        const reset = () => {
          if (quietTimer) clearTimeout(quietTimer);
          quietTimer = setTimeout(() => {
            if (!hasStopButton()) { 
              clearInterval(progressInterval);
              observer.disconnect(); 
              resolve(); 
            }
          }, quietMs);
        };
        const observer = new MutationObserver(reset);
        try { observer.observe(container, { childList: true, subtree: true, characterData: true }); } catch {}
        reset();
        setTimeout(() => { 
          clearInterval(progressInterval);
          observer.disconnect(); 
          reject(new Error(`waitForReplyComplete: timeout after ${Math.floor(timeoutMs/1000)}s`)); 
        }, timeoutMs);
      });
    }
  
    // ===== 保留你的完整链路 =====
    async function askAndGetReply(question, {
      quietMs = 1500,
      timeoutMs = 600000, // 增加到10分钟，适应长回复
    } = {}) {

      await sleep(1000);
        
        //问题输入框
      const div = await waitForSelector('#prompt-textarea, div#prompt-textarea').catch(() => null);
      if (!div) throw new Error('找不到 #prompt-textarea（div）');

      try { div.classList.add('ProseMirror-focused'); } catch {}

      // question的每一行都需要用一个p标签包裹，然后统一插入到div中
      const lines = question.split('\n');
      const pTags = lines.map(line => {
        const p = document.createElement('p');
        p.textContent = line;
        return p;
      });
      div.innerHTML = '';
      pTags.forEach(p => div.appendChild(p));

      div.dispatchEvent(new InputEvent('input', { bubbles: true }));
      div.dispatchEvent(new Event('change', { bubbles: true }));

      // 等待2秒, 让问题输入完成
      await sleep(2000);

      // 提交问题按钮
      const sendBtn = await waitForSelector('#composer-submit-button').catch(() => null);
      if (!sendBtn) throw new Error('找不到 #composer-submit-button 按钮');
      
      
      //触发按钮 - 提交问题
      sendBtn.dispatchEvent(new MouseEvent('click', { 
        bubbles: true, 
        cancelable: true 
      }));

      // 等待AI回复完成
      await waitForReplyComplete({ quietMs, timeoutMs, container: getChatRoot() });

      // 在当前的url上拼接参数
      const url = new URL(window.location.href);
      url.searchParams.set('role', 'worker');
      url.searchParams.set('taskId', sessionStorage.getItem('taskId'));
      window.history.pushState({}, '', url.toString());

      await sleep(5000);
      const answer_Code = await getAnswerFromCode();
      if (answer_Code) return answer_Code;
      const answer_P = await getAnswerFromP();
      if (answer_P) return answer_P;

      return null;
    }

    // ===== 工具 =====
    const sleep = (ms) => new Promise(r => setTimeout(r, ms));

    const getAnswerFromP = async (retries = 0) => {
        await sleep(5000);
        
        const ps = document.querySelectorAll('p[data-is-last-node=""][data-is-only-node=""]');
        if (ps.length == 0) {
            console.log('不存在p标签，快速返回1');
            return null;
        }

        const p = ps[ps.length - 1];
        if (!p) {
            console.log('不存在p标签，快速返回2');
            return null;
        }

        let prevTextLength_P = 0;

        // 连续3次稳定
        let stableCount = 0;
        let maxStableCount = 0;
        while (stableCount < 3 && maxStableCount < 30) { // 最多等待30秒
            maxStableCount++;

            if (!p.textContent) {
                console.log('p标签下不存在答案，重试1');
                stableCount = 0;
                await sleep(1000);
                continue;
            }

            if (stableCount == 0) {
                console.log('p标签下存在答案，稳定1');
                await sleep(1000);
                stableCount++;
                prevTextLength_P = p.textContent.length;
                continue;
            }

          if (p.textContent.length == prevTextLength_P) {
            console.log('p标签下存在答案，稳定'+stableCount);
            stableCount++;
          } else {
            console.log('p标签下存在答案，不稳定。重新检测稳定性。');
            stableCount = 0;
          }
          await sleep(1000);
        }

        if (maxStableCount >= 600000) {
            console.log('p标签下存在答案，不稳定次数超过'+600000+'次，返回null');
            return null;
        }
        
        let answer = '';
        // 按照BR标签切割
        const lines = p.textContent.split('<br>');
        lines.forEach(line => {
        // 只移除行首和行尾的双引号
        line = line.replace(/^"|"$/g, '');
        answer += line;
        });

        try {
            // 把json字符串转json对象
            const jsonObj = JSON.parse(answer);
            console.log('p标签下存在回复，json:', jsonObj);
        } catch (e) {
            console.log('p标签下存在回复，但不是json字符串，递归调用getAnswerFromP');
            await sleep(5000);
            if (retries >= 10) {
                console.log('p标签下存在回复，但不是json字符串，重试10次后返回null');
                return null;
            }
            return await getAnswerFromP(retries+1);
        }

        console.log('p标签下存在回复，返回答案');
        return answer;
      }

      // 从code标签中获取答案
      const getAnswerFromCode = async () => {
        await sleep(5000);

        const codes = document.querySelectorAll('code');
        if (codes.length == 0) { // 不存在code标签，快速返回
            console.log('不存在code标签，快速返回1');
            return null;
        }

        const code = codes[codes.length - 1];
        if (!code) {
            console.log('不存在code标签，快速返回2');
            return null;
        }

        let answer = null;
        let prevSpansLength = 0;

        // 连续3次稳定
        let stableCount = 0;
        let maxStableCount = 0;
        while (stableCount < 3 && maxStableCount < 30) { // 最多等待30秒
            maxStableCount++;

            const spans = code.querySelectorAll('span');
            if (spans.length == 0) {
                console.log('code下不存在span标签，重试1');
                stableCount = 0;
                await sleep(1000);
                continue;
            }

            if (stableCount == 0) {
                await sleep(1000);
                stableCount++;
                prevSpansLength = spans.length;
                console.log('code下存在span标签，稳定1');
                continue;
            }

            if (spans.length == prevSpansLength) {
                console.log('code下存在答案，稳定'+stableCount);
                stableCount++;
            } else {
                console.log('code下存在答案，不稳定。重新检测稳定性。');
                stableCount = 0;
            }
            await sleep(1000);
        }

        if (maxStableCount >= 30) {
            console.log('code下存在span标签，不稳定次数超过30次，返回null');
            return null;
        }

        // 获取code标签的直接子span元素，不是孙子span
        const sonSpans = code.querySelectorAll(':scope > span');
        if (sonSpans.length == 0) {
            console.log('code下不存在直接子span标签，返回null');
            return null;
        }

        const sonSpan = sonSpans[sonSpans.length - 1];
        if (!sonSpan) {
            console.log('code下不存在直接子span标签，返回null');
            return null;
        }

        const grandSonSpans = sonSpan.querySelectorAll(':scope > span');
        if (grandSonSpans.length == 0) {
            console.log('code下不存在直接子span标签，返回null');
            return null;
        }

        answer = '';
        // 遍历grandSonSpans，获取text
        grandSonSpans.forEach(span => {
            if (span.children.length > 0) {
                answer += span.children[0].textContent.replace(/\\n/g, '\n');
            }
        });

        try {
            // 把json字符串转json对象
            const jsonObj = JSON.parse(answer);
            console.log('code下存在回复，json元素数量:', jsonObj.length);
        } catch (e) {
            console.log('code下存在回复，但不是json字符串，递归调用getAnswerFromCode');
            console.log("answer:", answer);
            await sleep(5000);
            return await getAnswerFromCode();
        }
       

        console.log('code下存在回复，返回答案');
        return answer
      }

    const sendAnswerToAPI = async (answer_r) => {
        const taskId = sessionStorage.getItem('taskId');
        if (!taskId) {
            console.error('sendAnswerToAPI: taskId is not set');
            return;
        }

        // 如果answer_r为空，则不投送到API端
        if (!answer_r) {
            console.log('sendAnswerToAPI: answer_r is empty');
            return;
        }

        // 将回答投送到API端
        GM_xmlhttpRequest({
            method: 'POST',
            url: `${SERVER_URL}/answer?taskID=${taskId}`,
            headers: {
                'Content-Type': 'application/json'
            },
            data: JSON.stringify({
                message: answer_r
            }),
            onload: function(answerResponse) {
                try {
                    const answerResp = JSON.parse(answerResponse.responseText);
                    console.log('提交答案成功:', answerResp);

                    // 关闭窗口
                    window.close();
                } catch (error) {
                    console.error('解析答案响应失败:', error);
                }
            },
            onerror: function(error) {
                console.error('提交答案失败:', error);
            }
        });
    }
  
    // 导出 API
    Object.defineProperty(window, 'askAndGetReply', { value: askAndGetReply, configurable: true });
    Object.defineProperty(window, 'sendAnswerToAPI', { value: sendAnswerToAPI, configurable: true });
    Object.defineProperty(window, 'getAnswerFromCode', { value: getAnswerFromCode, configurable: true });
    Object.defineProperty(window, 'getAnswerFromP', { value: getAnswerFromP, configurable: true });

  
    // 全局错误处理
    window.addEventListener('error', function(e) {
      console.error('全局错误:', e.error);
    });
    
    window.addEventListener('unhandledrejection', function(e) {
      console.error('未处理的Promise拒绝:', e.reason);
      // 防止默认的未处理拒绝行为
      e.preventDefault();
    });

    
  
    // demo（默认关）
    (async () => {
      try {
        // 获取任务ID
        const taskId = urlParams.get('taskId');
        if (!taskId) {
            console.error('taskId is not set');
            return;
        }
        // taskID 写入 sessionStorage
        sessionStorage.setItem('taskId', taskId);

        // 等待2秒，让页面加载完成
        await sleep(5000);

        // 先检测是否存在回复完成的数据，如果存在，直接投送到API端
        const answer = await getAnswerFromCode();
        if (answer) {
            sendAnswerToAPI(answer);
            return;
        }
        const answer_P = await getAnswerFromP();
        if (answer_P) {
            sendAnswerToAPI(answer_P);
            return;
        }

        // 根据任务ID去服务器获取任务详情
        GM_xmlhttpRequest({
            method: 'GET',
            url: `${SERVER_URL}/question?taskID=${taskId}`,
            headers: {
                'Content-Type': 'application/json'
            },
            onload: async function(response) {
                try {
                    const resp = JSON.parse(response.responseText);
                    console.log('获取任务详情成功:', resp);
                    
                    // 根据任务详情，调用askAndGetReply函数，获取回答
                    console.log('开始处理任务，问题:', resp.question);

                    if (resp.question == "") {
                      console.log('问题为空，关闭窗口');
                      window.close();
                      return;
                    }

                    console.log('使用超时配置:', {
                        timeoutMs: resp.options?.timeoutMs || 600000, // 默认10分钟
                        quietMs: resp.options?.quietMs || 1500,
                        interceptWaitMs: resp.options?.interceptWaitMs || 1500
                    });
                    
                    const txt = await askAndGetReply(resp.question, {
                        ...resp.options,
                        timeoutMs: resp.options?.timeoutMs || 600000, // 确保超时时间足够
                        quietMs: resp.options?.quietMs || 1500,
                        interceptWaitMs: resp.options?.interceptWaitMs || 1500
                    });
                    console.log('获取回答成功，长度:', txt.length);
                    
                    // 投送到API端
                    sendAnswerToAPI(txt);
                } catch (error) {
                    console.error('处理任务详情失败:', error);
                }
            },
            onerror: function(error) {
                console.error('获取任务详情失败:', error);
            }
        });
      } catch (e) {
        console.warn('[page] main error:', e);
      }
    })();
  })();
  